
/* GCSx
** MEM.CPP
**
** Memory functions that perform full error reporting and garbage collection
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

const char memFail[] = "Out of Memory Error";
#define MAX_DETAILS 255
char details[MAX_DETAILS];
char details2[MAX_DETAILS * 2];

void fatalCrash(int memoryError, const char* title, ...) {
    va_list arglist;
    va_start(arglist, title);

    vsnprintf(details, MAX_DETAILS, title, arglist);
    snprintf(details2, MAX_DETAILS * 2, PRODUCT_NAME " will shutdown. Details:\n%s\n\nYour work has been saved as:\n@TODO:", details);
    
    // @TODO: Attempt to save work in progress (at least if !memoryError)
    // We'll have to check other places this comes from to make sure there's
    // no saving restrictions (such as fatalCrash during another save)
    systemErrorBox(details2, "Fatal Error");
    exit(-1);

    // (never reach here)
    va_end(arglist);
}

#ifndef MEMDEBUG

// These versions are non-debugging and slim- eventually, there may be
// garbage collection (e.g. clearing of unneeded memory when an alloc fails)

void* operator new(std::size_t size, const std::nothrow_t&) {
    void* ptr = malloc(size);
    return ptr;
}

void* operator new(std::size_t size) {
    void* ptr = malloc(size);

    if (ptr == NULL) {
        fatalCrash(1, memFail);
    }
    
    return ptr;
}

void* operator new[](std::size_t size, const std::nothrow_t&) {
    void* ptr = malloc(size);
    return ptr;
}

void* operator new[](std::size_t size) {
    void* ptr = malloc(size);

    if (ptr == NULL) {
        fatalCrash(1, memFail);
    }

    return ptr;
}

void operator delete(void* ptr, const std::nothrow_t&) {
    if (ptr == NULL) return;
    free(ptr);
}

void operator delete(void* ptr) {
    if (ptr == NULL) return;
    free(ptr);
}

void operator delete[](void* ptr, const std::nothrow_t&) {
    if (ptr == NULL) return;
    free(ptr);
}

void operator delete[](void* ptr) {
    if (ptr == NULL) return;
    free(ptr);
}

#else

volatile std::size_t memThreshold = 0;
const char* memFromFile = NULL;
int memMaintainList = 0;
int memDebugErrors = 0;
int function_begin_end::level = 0;

void setMemThreshold(std::size_t thresh) {
    memThreshold = thresh;
}

function_begin_end::function_begin_end(const char* n) {
    previous = memFromFile;
    memFromFile = n;
    name = n;
    debugStdout(DEBUG_STACK, "trace: (%d) begin %s", ++level, n);
}

function_begin_end::~function_begin_end() {
    debugStdout(DEBUG_STACK, "trace: (%d) end   %s", level--, name);
    memFromFile = previous;
}

void enableMemListTrack() {
    memMaintainList = 1;
}

int memDebugAnyErrors() {
    return memDebugErrors;
}

struct memDebug {
public:
    // Maintain circular linked list of all memory allocations
    memDebug* previousAlloc;
    memDebug* nextAlloc;

    enum {
        MEMDEBUG_STATUS_ARRAY = 1,
        MEMDEBUG_STATUS_DELETED = 2,
        MEMDEBUG_STATUS_MAX = 3,
        MEMDEBUG_MAGIC_COOKIE = 0x75BA23E1,
        MEMDEBUG_BOUNDS_PADDING = 16,
        MEMDEBUG_BOUNDS_COOKIE = 0x55,
    };
    
    // 32 bits to keep structure sized evenly
    Uint32 status;
    // Size of allocation NOT including structure/bounds
    Uint32 size; 
    Uint32 magicCookie;
    const char* fromFile;
};

// First link in list
memDebug firstMemDebug = { NULL, NULL, 0, 0, memDebug::MEMDEBUG_MAGIC_COOKIE, NULL };

void memLeakCheck() {
    if ((memMaintainList) && (firstMemDebug.nextAlloc)) {
        memDebugErrors = 1;
        debugStdout("undeallocated memory: (leaks)");
        memDebug* report = firstMemDebug.nextAlloc;
        int leaks = 0;
        while ((report != &firstMemDebug) && (report)) {
            const char* error = NULL;
            if ((report->magicCookie == memDebug::MEMDEBUG_MAGIC_COOKIE) &&
                (report->status <= memDebug::MEMDEBUG_STATUS_MAX)) {
                // Marked as deleted?
                if (report->status & memDebug::MEMDEBUG_STATUS_DELETED) {
                    error = "corrupted list (deleted item in list)";
                }
                else {
                    // Verify circular link integrity
                    if ((report->nextAlloc->previousAlloc != report) ||
                        (report->previousAlloc->nextAlloc != report)) {
                        error = "corruption in circular links";
                    }
                }
            }
            else {
                error = "corrupted list (corrupted item in list)";
            }
            if (error) {
                debugStdout("error in list: %s", error);
                report = NULL;
            }
            else {
                debugStdout("allocation %p for %d bytes", report, report->size);
                debugDump(report, min(report->size, (Uint32)32), 1);
                if (report->fromFile) {
                    debugStdout("allocation from: %s", report->fromFile);
                }
                else {
                    debugStdout("(unknown allocation location)");
                }
                report = report->nextAlloc;
                ++leaks;
            }
        }
        debugStdout("%d leaks listed", leaks);
        memMaintainList = 0;
    }
    else if (memMaintainList) {
        debugStdout("No memory leaks detected");
    }
}

/*
// Non-inline versions, if needed

void* operator new(std::size_t size, const std::nothrow_t&) {
    return new_debug(size, 0, 1);
}

void* operator new(std::size_t size) {
    return new_debug(size, 0, 0);
}

void* operator new[](std::size_t size, const std::nothrow_t&) {
    return new_debug(size, 1, 1);
}

void* operator new[](std::size_t size) {
    return new_debug(size, 1, 0);
}

void operator delete(void* ptr, const std::nothrow_t&) {
    new_delete(ptr, 0);
}

void operator delete(void* ptr) {
    new_delete(ptr, 0);
}

void operator delete[](void* ptr, const std::nothrow_t&) {
    new_delete(ptr, 1);
}

void operator delete[](void* ptr) {
    new_delete(ptr, 1);
}

*/

void* new_debug(std::size_t size, int array, int noThrow) {
    if (memThreshold) {
        if (size > memThreshold) {
            if (noThrow) return NULL;
            fatalCrash(1, memFail);
        }
    }
    
    void* ptr = malloc(size + sizeof(memDebug) + memDebug::MEMDEBUG_BOUNDS_PADDING * 2);

    if (ptr == NULL) {
        if (noThrow) return NULL;
        fatalCrash(1, memFail);
    }

    // Maintain circular list
    if (memMaintainList) {
        if (firstMemDebug.previousAlloc) {
            ((memDebug*)ptr)->previousAlloc = firstMemDebug.previousAlloc;
            ((memDebug*)ptr)->nextAlloc = &firstMemDebug;
            firstMemDebug.previousAlloc->nextAlloc = (memDebug*)ptr;
            firstMemDebug.previousAlloc = (memDebug*)ptr;
        }
        else {
            // First item in list
            ((memDebug*)ptr)->previousAlloc = &firstMemDebug;
            ((memDebug*)ptr)->nextAlloc = &firstMemDebug;
            firstMemDebug.previousAlloc = (memDebug*)ptr;
            firstMemDebug.nextAlloc = (memDebug*)ptr;
        }
    }
    else {
        ((memDebug*)ptr)->previousAlloc = NULL;
        ((memDebug*)ptr)->nextAlloc = NULL;
    }

    // Bounds checking
    Uint8* start = ((Uint8*)ptr) + sizeof(memDebug);
    for (int i = 0; i < memDebug::MEMDEBUG_BOUNDS_PADDING; ++i, ++start) {
        *start = memDebug::MEMDEBUG_BOUNDS_COOKIE;
    }
    start += size;
    for (int i = 0; i < memDebug::MEMDEBUG_BOUNDS_PADDING; ++i, ++start) {
        *start = memDebug::MEMDEBUG_BOUNDS_COOKIE;
    }
    
    // Structure data
    ((memDebug*)ptr)->status = array ? memDebug::MEMDEBUG_STATUS_ARRAY : 0;
    ((memDebug*)ptr)->size = size;
    ((memDebug*)ptr)->magicCookie = memDebug::MEMDEBUG_MAGIC_COOKIE;
    if (memFromFile) {
        ((memDebug*)ptr)->fromFile = memFromFile;
    }
    else {
        ((memDebug*)ptr)->fromFile = NULL;
    }

    debugStdout(DEBUG_MEMORY, array ? "memory: new[] %p (%d)" : "memory: new   %p (%d)", ptr, size);
    return ((Uint8*)ptr) + sizeof(memDebug) + memDebug::MEMDEBUG_BOUNDS_PADDING;
}

void new_delete(void* ptr, int array) {
    if (ptr == NULL) {
        debugStdout(DEBUG_MEMORY, array ? "memory: del[] NULL" : "memory: del   NULL");
        return;
    }
    
    ptr = ((Uint8*)ptr) - sizeof(memDebug) - memDebug::MEMDEBUG_BOUNDS_PADDING;
    
    // Verify structure data
    const char* error = NULL;
    const char* fromFile = NULL;
    if ((((memDebug*)ptr)->magicCookie == memDebug::MEMDEBUG_MAGIC_COOKIE) &&
        (((memDebug*)ptr)->status <= memDebug::MEMDEBUG_STATUS_MAX)) {
        // (now that we know we can probably trust this pointer)
        fromFile = ((memDebug*)ptr)->fromFile;
        // Marked as deleted?
        if (((memDebug*)ptr)->status & memDebug::MEMDEBUG_STATUS_DELETED) {
            error = "duplicate deletion";
        }
        else {
            // Verify circular link integrity
            if ((((memDebug*)ptr)->nextAlloc) && (((memDebug*)ptr)->previousAlloc)) {
                if ((((memDebug*)ptr)->nextAlloc->previousAlloc == (memDebug*)ptr) &&
                    (((memDebug*)ptr)->previousAlloc->nextAlloc == (memDebug*)ptr)) {
                    // Remove from circular linked list
                    if (((memDebug*)ptr)->nextAlloc == ((memDebug*)ptr)->previousAlloc) {
                        // First item in list
                        firstMemDebug.previousAlloc = NULL;
                        firstMemDebug.nextAlloc = NULL;
                    }
                    else {
                        ((memDebug*)ptr)->previousAlloc->nextAlloc = ((memDebug*)ptr)->nextAlloc;
                        ((memDebug*)ptr)->nextAlloc->previousAlloc = ((memDebug*)ptr)->previousAlloc;
                    }
                }
                else {
                    error = "corruption in circular links (probably duplicate deletion)";
                }
            }
            if (!error) {
                // Check bounds
                Uint8* start = ((Uint8*)ptr) + sizeof(memDebug);
                for (int i = 0; i < memDebug::MEMDEBUG_BOUNDS_PADDING; ++i, ++start) {
                    if (*start != memDebug::MEMDEBUG_BOUNDS_COOKIE) {
                        error = "lower bound overflow";
                        break;
                    }
                }
                start += ((memDebug*)ptr)->size;
                for (int i = 0; i < memDebug::MEMDEBUG_BOUNDS_PADDING; ++i, ++start) {
                    if (*start != memDebug::MEMDEBUG_BOUNDS_COOKIE) {
                        error = "upper bound overflow";
                        break;
                    }
                }
                // Check type
                if ((!array) && (((memDebug*)ptr)->status & memDebug::MEMDEBUG_STATUS_ARRAY)) {
                    error = "delete used on array";
                }
                if ((array) && (!(((memDebug*)ptr)->status & memDebug::MEMDEBUG_STATUS_ARRAY))) {
                    error = "delete[] used on non-array";
                }
            }
            // Mark as deleted
            ((memDebug*)ptr)->status |= memDebug::MEMDEBUG_STATUS_DELETED;
        }
    }
    else {
        error = "deleting non-allocated pointer (possibly duplicate deletion)";
    }
    if (error) {
        memDebugErrors = 1;
        debugStdout("memory error: %s", error);
        if (fromFile) {
            debugStdout("allocation from: %s", fromFile);
            if (memFromFile) {
                debugStdout("deallocation at: %s", memFromFile);
            }
        }
        else {
            debugStdout("(unknown allocation location)");
            if (memFromFile) {
                debugStdout("deallocation might be at: %s", memFromFile);
            }
        }
    }

    debugStdout(DEBUG_MEMORY, array ? "memory: del[] %p" : "memory: del   %p", ptr);
    free(ptr);
}

#endif
